#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

# bnetd.connection_plugin - library routine for connection plugin
#
# Copyright (c) 2005-2020 Fpemud <fpemud@sina.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN OBCONN WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

"""
@author: Fpemud
@license: GPLv3 License
@contact: fpemud@sina.com
"""

import sys
import json
import socket

__author__ = "fpemud@sina.com (Fpemud)"
__version__ = "0.0.1"


class PluginBase:

    async def config_changed(self, new_config):
        raise NotImplementedError()

    async def connection_file_added(self, connection_file_name, connection_file_content):
        """
        returns True: this is my file and I have processed it
        returns False: this is not my file
        """
        raise NotImplementedError()

    async def connection_file_removed(self, connection_file_name):
        raise NotImplementedError()

    async def get_all_connections(self, query=None):
        """
        returns dict<connection-id,connection-data>
        returns all the connections (including avaiable connections and unavaiable connections)
        """
        raise NotImplementedError()

    async def get_connection(self, connection_id):
        """
        returns connection-data
        """
        raise NotImplementedError()

    async def start(self, callbacks):
        raise NotImplementedError()

    async def stop(self):
        raise NotImplementedError()

    async def refresh(self):
        """
        re-test connection-available etc.
        """
        assert False

    async def enable_connection(self, connection_id):
        assert False

    async def disable_connection(self, connection_id):
        assert False


class PluginCallback:

    def connection_available(self, connection_id):
        raise NotImplementedError()

    def connection_unavailable(self, connection_id, reason):
        raise NotImplementedError()

    def connection_activated(self, connection_id, connection_livedata):
        """
        connection_livedata example:
        {
            "interface": [
                {
                    "ifname": "INTERFACE-NAME",
                    "ifconfig": {
                        "inet": {
                            "address": "X.X.X.X",
                            "prefixlen": N,
                        },
                        "inet6": {
                            "address": "X.X.X.X",
                            "prefixlen": N,
                        },
                    }
                    "route": {
                        "inet": [
                            {
                                "target-network": [
                                    "- 10.0.0.0/8",
                                    "+ 0.0.0.0/0",
                                ],
                                "nexthop": [ "IP", "IP" ],
                            },
                        ]
                        "inet6": [
                            {
                                "target-network": [
                                    "- XXX",
                                    "+ XXX",
                                ],
                                "nexthop": [ "IP", "IP" ],
                            },
                        ]
                    }
                    "nameserver": [
                        {
                            "target-domain": [
                                "- google.com",
                                "+ .",
                            ],
                            "protocol": "dns",
                            "inet": [ "IP", "IP:PORT" ],
                            "inet6": [ "IP", "IP:PORT" ],
                        },
                    ],
                    "http-proxy": [
                        {
                            "target-domain": [
                                "- google.com",
                                "+ .",
                            ],
                            "target-network": [
                                "- 10.0.0.0/8",
                                "+ 0.0.0.0/0",
                            ],
                            "inet": [ "IP:PORT", "IP:PORT" ],
                            "inet6": [ "IP:PORT", "IP:PORT" ],
                            "unix": [ "PATH", "PATH" ],
                        },
                    ],
                    "socks-proxy": {
                        XXX,
                    },
                    "ntp-server": {
                        "inet": [ IP, IP ],
                        "inet6": [ IP, IP ],
                    },
                    "wins-server": {
                        "inet": [ IP, IP ],
                        "inet6": [ IP, IP ],
                    },
                },
            ],
            "http-proxy": [
                {
                    "target-domain": [
                        "- google.com",
                        "+ .",
                    ],
                    "target-network": [
                        "- 10.0.0.0/8",
                        "+ 0.0.0.0/0",
                    ],
                    "inet": [ "127.0.0.1:PORT", "127.0.0.1:PORT" ],
                    "inet6": [ "::1:PORT", "::1:PORT" ],
                    "unix": [ "PATH", "PATH" ],
                },
            ],
            "socks-proxy": [
                {
                    XXX,
                },
            ],
        }
        """
        pass

    def connection_deactivated(self, connection_id, reason):
        pass


class PluginMain:

    def __init__(self):
        self._param = json.loads(sys.argv[1])
        self._endpoint = _util.EndPoint(self._param["socket-file"], self)
        self._mainloop = asyncio.get_event_loop()
        self._pobj = None

    def run(self, plugin_object):
        self._pobj = plugin_object
        try:
            self._endpoint.open()

            self._mainloop.add_signal_handler(signal.SIGINT, self._sigHandlerINT)
            self._mainloop.add_signal_handler(signal.SIGTERM, self._sigHandlerTERM)
            self._mainloop.run_forever()
        finally:
            self._mainloop.run_until_complete(self._pobj.stop())
            self._endpoint.close()

    async def on_command(self):
        ret = None

        command = self._endpoint.get_command()
        if command == "config-changed":
            args = self._endpoint.get_command_parameters("new-config")
            await self._pobj.config_changed(*args)
        elif command == "connection-file-added":
            args = self._endpoint.get_command_parameters("connection-file-name", "connection-file-content")
            ret = await self._pobj.config_changed(*args)
        elif command == "connection-file-removed":
            args = self._endpoint.get_command_parameters("connection-file-name")
            await self._pobj.config_changed(*args)
        elif command == "get-all-connections":
            args = self._endpoint.get_command_parameters("query")
            ret = await self._pobj.get_all_connections(*args)
        elif command == "get-connection":
            args = self._endpoint.get_command_parameters("connection-id")
            ret = await self._pobj.get_connection(*args)
        elif command == "start":
            self._endpoint.no_command_parameter_needed()
            await self._pobj.start(_PluginCallback(self._endpoint))
        elif command == "stop":
            self._endpoint.no_command_parameter_needed()
            await self._pobj.stop()
        elif command == "refresh":
            self._endpoint.no_command_parameter_needed()
            await self._pobj.refresh()
        elif command == "enable-connection":
            args = self._endpoint.get_command_parameters("connection-id")
            await self._pobj.enable_connection(*args)
        elif command == "disable-connection":
            args = self._endpoint.get_command_parameters("connection-id")
            await self._pobj.disable_connection(*args)
        else:
            self._endpoint.no_command_found()

        return ret

    def _sigHandlerINT(self, signum):
        logging.info("SIGINT received.")
        self._mainloop.stop()
        return True

    def _sigHandlerTERM(self, signum):
        logging.info("SIGTERM received.")
        self._mainloop.stop()
        return True


class _PluginCallback(PluginCallback):

    def __init__(self, endpoint):
        self._endpoint = endpoint

    def connection_available(self, connection_id):
        await self._endpoint.send_notification("connection-available", {
            "connection-id": connection_id,
        })

    def connection_unavailable(self, connection_id, reason):
        await self._endpoint.send_notification("connection-unavailable", {
            "connection-id": connection_id,
            "reason": reason,
        })

    def connection_activated(self, connection_id, connection_livedata):
        await self._endpoint.send_notification("connection-activated", {
            "connection-id": connection_id,
            "connection-livedata": connection_livedata,
        })

    def connection_deactivated(self, connection_id, reason):
        await self._endpoint.send_notification("connection-deactivated", {
            "connection-id": connection_id,
            "reason": reason,
        })
